﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TharpPhoneBookConsoleLab.Model;
using TharpPhoneBookConsoleLab.Controller;

namespace TharpPhoneBookConsoleLab.View
{
    class ProgramMain
    {
        
        public static void Main(string[] args)
        {
            var book = new PhoneBook();
            var bookControls = new PhoneBookController(book);
            var consoleApp = new PhoneBookConsoleUI(bookControls);
            consoleApp.Run();
        }//end ProgramMain
    }//end class ProgramMain

    class PhoneBookConsoleUI
    {
        #region Fields

        private PhoneBookController _Controller;
        private List<PhoneBookConsoleCommand> _ValidCommands;

        #endregion

        #region Constructor

        /// <summary>
        /// Assigns the Controller and maps the list of valid user commands to the Controller's operations
        /// </summary>
        /// <param name="controller">A PhoneBookController</param>
        public PhoneBookConsoleUI(PhoneBookController controller)
        {
            _Controller = controller;
            _ValidCommands =
                new List<PhoneBookConsoleCommand>
                {
                    new PhoneBookConsoleCommand
                    {
                        CommandString = "l",
                        Description = "Load data from file",
                        NumArgs = 1,
                        ParseInput = stringList => new PhoneBookOpArgs{FileName = stringList[0]},
                        Operation = args => new PhoneBookOpArgs{Message=_Controller.Load(args.FileName)},
                        OutputSuccess = param => "Data loaded.",
                    },
                    new PhoneBookConsoleCommand
                    {
                        CommandString = "s",
                        Description = "Store data to file",
                        NumArgs = 1,
                        ParseInput = stringList => new PhoneBookOpArgs{FileName = stringList[0]},
                        Operation = args => new PhoneBookOpArgs{Message=_Controller.Save(args.FileName)},
                        OutputSuccess = param => "Phone book stored."
                    },
                    new PhoneBookConsoleCommand
                    {
                        CommandString = "p",
                        Description = "Put entry in phone book",
                        NumArgs = 2,
                        ParseInput = stringList => new PhoneBookOpArgs{Entry = new PhoneBookEntry(stringList[0],stringList[1])},
                        Operation = args => {_Controller.Put(args.Entry); return new PhoneBookOpArgs();},
                        OutputSuccess = param => "Added."
                    },
                    new PhoneBookConsoleCommand
                    {
                        CommandString = "g",
                        Description = "Get number for name in phone book",
                        NumArgs = 1,
                        ParseInput = stringList => new PhoneBookOpArgs{Entry = new PhoneBookEntry(stringList[0],null)},
                        Operation = args => new PhoneBookOpArgs{Entry=_Controller.Get(args.Entry.Name)},
                        OutputSuccess = args => args.Entry == null ? "Not found." : args.Entry.Name.DisplayText + ": " + args.Entry.Number.DisplayText
                    },
                    new PhoneBookConsoleCommand
                    {
                        CommandString = "q",
                        Description = "Quit application",
                        NumArgs = 0,
                        ParseInput = param => new PhoneBookOpArgs(),
                        Operation = args => {Console.WriteLine("Good-bye."); Environment.Exit(0); return new PhoneBookOpArgs();},
                    }
                };
        }//end PhoneBookUI

        #endregion

        #region Public Interface

        /// <summary>
        /// Launches the UI
        /// </summary>
        public void Run()
        {
            while (true)
            {
                //read user input
                string userInput = System.Console.ReadLine();

                //get the command code
                var inputFields = userInput.Split().ToList<string>();
                var commandString = inputFields[0];

                //validate the command code
                var command = FindCommand(commandString);
                if (command == null)
                {
                    //command wasn't found, display the help text
                    Console.WriteLine(HelpText());
                }
                //otherwise, validate the arguments
                else if (inputFields.Count - 1 != command.NumArgs)
                        Console.WriteLine("Error: Invalid number of arguments. That command takes {0} arguments.", command.NumArgs);
                else
                {
                    inputFields.RemoveAt(0);
                    var argumentString = inputFields.Any() ? inputFields.Aggregate((s1, s2) => s1 + " " + s2) : String.Empty;
                    if (command.InputValidator != null && !command.InputValidator(argumentString))
                        Console.WriteLine("Error: Invalid arguments");
                    else //if arguments are good, run the command
                    {
                        var arguments = command.ParseInput(inputFields.ToList());
                        var operationOutput = command.Operation(arguments);

                        //Output either the operation's Message, or the success message
                        var outputString = operationOutput.Message ?? command.OutputSuccess(operationOutput);
                        Console.WriteLine(outputString);
                    }
                }
            }//end while

        }//end Run

        #endregion

        #region Private Helpers

        private string HelpText()
        {
            return String.Concat(
                from cmd in _ValidCommands
                select "  " + cmd.CommandString + " " + cmd.Description + System.Environment.NewLine);
        }//end HelpText

        private PhoneBookConsoleCommand FindCommand(string commandString)
        {
            var cmds = from cmd in _ValidCommands where cmd.CommandString == commandString select cmd;
            if (!cmds.Any())
                return null;
            return cmds.First();
        }//end FindCommand

        private class PhoneBookOpArgs
        {
            public PhoneBookEntry Entry { get; set; }
            public String FileName { get; set; }
            public String Message { get; set; }
        }//end class PhoneBookOpArgs

        private class PhoneBookConsoleCommand
        {
            /// <summary>
            /// The user input command code
            /// </summary>
            public string CommandString { get; set; }

            /// <summary>
            /// The help-text description
            /// </summary>
            public string Description { get; set; }

            /// <summary>
            /// Number of arguments expected
            /// </summary>
            public int NumArgs { get; set; }

            /// <summary>
            /// Takes an input string and returns whether or not the input is valid
            /// </summary>
            public Predicate<string> InputValidator { get; set; }

            /// <summary>
            /// Parses a list of input strings into a PhoneBookOpArgs object
            /// </summary>
            public Func<List<String>,PhoneBookOpArgs> ParseInput { get; set; }

            /// <summary>
            /// Consumes a PhoneBookOpArgs and executes the command.
            /// Returns a PhoneBookOpArgs with the Message set to an error code, if applicable
            /// </summary>
            public Func<PhoneBookOpArgs,PhoneBookOpArgs> Operation { get; set; }

            /// <summary>
            /// Constructs the message to be displayed if the operation was successful,
            /// given a PhoneBookOpArgs representing the result of the operation
            /// </summary>
            public Func<PhoneBookOpArgs,String> OutputSuccess { get;set; }
            
        }//end class PhoneBookConsoleCommand

        #endregion

    }//end class PhoneBookConsoleUI

    
}
